#include <linux/module.h>
#include <linux/device.h>
#include <linux/cdev.h>

MODULE_LICENSE("Dual BSD/GPL");

static dev_t major = -1;
static int const devcnt = 1;
struct cdev cdev;
struct device *char_dev = NULL;
static struct class *chartest_class;

static ssize_t chardev_dev_read(struct file *file, char __user * buf,
				size_t count, loff_t * offset)
{
	printk(KERN_INFO "%s\n", __func__);
	return 0;
}

static ssize_t chardev_dev_write(struct file *file, const char __user * buf,
				 size_t count, loff_t * offset)
{
	printk(KERN_INFO "write:%s\n", buf);
	printk(KERN_INFO "%s\n", __func__);
	return count;
}

static long chardev_dev_ioctl(struct file *file, unsigned int cmd,
			      unsigned long arg)
{
	printk(KERN_INFO "%s\n", __func__);
	return 0;
}

static int chardev_dev_open(struct inode *inode, struct file *file)
{
	printk(KERN_INFO "%s\n", __func__);
	return 0;
}

static int chardev_dev_release(struct inode *inode, struct file *file)
{
	printk(KERN_INFO "%s\n", __func__);
	return 0;
}

static const struct file_operations chardev_dev_fops = {
	.owner = THIS_MODULE,
	.llseek = no_llseek,
	.read = chardev_dev_read,
	.write = chardev_dev_write,
	.unlocked_ioctl = chardev_dev_ioctl,
	.open = chardev_dev_open,
	.release = chardev_dev_release,
};

static int hello_init(void)
{
	unsigned int err;

	cdev_init(&cdev, &chardev_dev_fops);

	err = alloc_chrdev_region(&major, 0, devcnt, "unused");
	if (err < 0) {
		unregister_chrdev_region(major, devcnt);
		printk(KERN_ALERT "alloc_chrdev_region error\n");
		goto err_fun;
	}
	printk(KERN_INFO "alloc_chrdev_region\n");

	err = cdev_add(&cdev, major, 1);
	if (err < 0) {
		printk(KERN_ALERT "cdev_add error\n");
		err = -1;
		goto err_fun;
	}
	printk(KERN_INFO "cdev_add\n");

	chartest_class = class_create(THIS_MODULE, "testclass");
	if (IS_ERR(chartest_class)) {
		pr_err("failed to create class %ld\n", PTR_ERR(chartest_class));
		err = -1;
		goto err_fun;
	}
	printk(KERN_INFO "class_create testclass\n");

	char_dev =
	    device_create(chartest_class, NULL, major, NULL, "%s%d", "chartest",
			  1);
	if (IS_ERR(char_dev)) {
		printk(KERN_ALERT "device_create error\n");
		err = -1;
		goto err_fun;
	}
	printk(KERN_INFO "device_create chartest success\n");

	return 0;

err_fun:
	if (!IS_ERR(char_dev)) {
		device_destroy(chartest_class, major);
	}

	if (!IS_ERR(chartest_class)) {
		class_destroy(chartest_class);
	}

	if (-1 != major) {
		unregister_chrdev(major, "test");
		major = -1;
	}

	return err;
}

static void hello_exit(void)
{

	if (!IS_ERR(char_dev)) {
		device_destroy(chartest_class, major);
	}

	if (!IS_ERR(chartest_class)) {
		class_destroy(chartest_class);
	}

	if (-1 != major) {
		unregister_chrdev(major, "test");
		major = -1;
	}

	cdev_del(&cdev);

	printk(KERN_INFO "%s\n", __func__);
}

module_init(hello_init);
module_exit(hello_exit);
